package com.small.nacos.naming.controllers;

import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.CommonParams;
import com.alibaba.nacos.api.naming.NamingResponseCode;
import com.alibaba.nacos.api.naming.PreservedMetadataKeys;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.small.nacos.naming.core.Instance;
import com.small.nacos.naming.core.Service;
import com.small.nacos.naming.core.ServiceManager;
import com.small.nacos.naming.core.utils.WebUtils;
import com.small.nacos.naming.healthcheck.RsInfo;
import com.small.nacos.naming.misc.SwitchEntry;
import com.small.nacos.naming.misc.SwitchDomain;
import com.small.nacos.naming.misc.UtilsAndCommons;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;

/**
 * @Author zhoujin
 * @Date 2022/7/22 20:48
 */
@RestController
@RequestMapping(UtilsAndCommons.NACOS_NAMING_CONTEXT + "/instance")
public class InstanceController {

    @Autowired
    private ServiceManager serviceManager;

    @Autowired
    private SwitchDomain switchDomain;

    @PostMapping
    public String register(HttpServletRequest request) throws Exception {

        final String namespaceId = request.getParameter(CommonParams.NAMESPACE_ID);
        final String serviceName = request.getParameter(CommonParams.SERVICE_NAME);
        final Instance instance = parseInstance(request);

        serviceManager.registerInstance(namespaceId, serviceName, instance);
        return "ok";
    }

    @DeleteMapping
    public String deregister(HttpServletRequest request) throws Exception {

        Instance instance = getIpAddress(request);

        String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
        final String serviceName = request.getParameter(CommonParams.SERVICE_NAME);

        Service service = serviceManager.getService(namespaceId, serviceName);
        if (service == null) {
            return "ok";
        }

        serviceManager.removeInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
        return "ok";
    }

    @GetMapping("/list")
    public ObjectNode list(HttpServletRequest request) throws Exception {

        String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
        String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
        String clusters = WebUtils.optional(request, "clusters", StringUtils.EMPTY);
        return doSrvIpxt(namespaceId, serviceName,clusters);
    }

    @PutMapping("/beat")
    public ObjectNode beat(HttpServletRequest request) throws Exception {

        ObjectNode result = JacksonUtils.createEmptyJsonNode();

        //放入下一次心跳请求时间
        result.put(SwitchEntry.CLIENT_BEAT_INTERVAL, switchDomain.getClientBeatInterval());

        String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
        String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
        String beat = WebUtils.optional(request, "beat", StringUtils.EMPTY);

        RsInfo clientBeat = null;
        if (StringUtils.isNotBlank(beat)) {
            clientBeat = JacksonUtils.toObj(beat, RsInfo.class);
        }
        String clusterName = WebUtils
                .optional(request, CommonParams.CLUSTER_NAME, UtilsAndCommons.DEFAULT_CLUSTER_NAME);
        String ip = WebUtils.optional(request, "ip", StringUtils.EMPTY);
        int port = Integer.parseInt(WebUtils.optional(request, "port", "0"));

        //从心跳信息中获取
        if (clientBeat != null) {
            if (StringUtils.isNotBlank(clientBeat.getCluster())) {
                clusterName = clientBeat.getCluster();
            } else {
                clientBeat.setCluster(clusterName);
            }
            ip = clientBeat.getIp();
            port = clientBeat.getPort();
        }

        Instance instance = serviceManager.getInstance(namespaceId, serviceName, clusterName, ip, port,true);

        if (instance == null){
            if (clientBeat == null){
                result.put(CommonParams.CODE, NamingResponseCode.RESOURCE_NOT_FOUND);
                return result;
            }
            instance = new Instance();
            instance.setPort(clientBeat.getPort());
            instance.setIp(clientBeat.getIp());
            instance.setWeight(clientBeat.getWeight());
            instance.setMetadata(clientBeat.getMetadata());
            instance.setClusterName(clusterName);
            instance.setServiceName(serviceName);
            instance.setInstanceId(instance.getInstanceId());
            instance.setEphemeral(clientBeat.isEphemeral());
            serviceManager.registerInstance(namespaceId,serviceName,instance);
        }
        Service service = serviceManager.getService(namespaceId, serviceName);
        if (service == null) {
            throw new NacosException(NacosException.SERVER_ERROR,
                    "service not found: " + serviceName + "@" + namespaceId);
        }
        if (clientBeat == null) {
            clientBeat = new RsInfo();
            clientBeat.setIp(ip);
            clientBeat.setPort(port);
            clientBeat.setCluster(clusterName);
        }
        service.processClientBeat(clientBeat);

        result.put(CommonParams.CODE, NamingResponseCode.OK);
        if (instance.containsMetadata(PreservedMetadataKeys.HEART_BEAT_INTERVAL)) {
            result.put(SwitchEntry.CLIENT_BEAT_INTERVAL, instance.getInstanceHeartBeatInterval());
        }
        result.put(SwitchEntry.LIGHT_BEAT_ENABLED, switchDomain.isLightBeatEnabled());
        return result;
    }

    private ObjectNode doSrvIpxt(String namespaceId, String serviceName,String clusters) {

        Service service = serviceManager.getService(namespaceId, serviceName);
        ObjectNode result = JacksonUtils.createEmptyJsonNode();
        if (service == null){
            result.put("name", serviceName);
            result.replace("hosts", JacksonUtils.createEmptyArrayNode());
            return result;
        }

        List<Instance> srvedIPs = service.srvIPs(Arrays.asList(StringUtils.split(clusters, ",")));

        ArrayNode hosts = JacksonUtils.createEmptyArrayNode();

        for (Instance instance : srvedIPs) {

            ObjectNode ipObj = JacksonUtils.createEmptyJsonNode();
            ipObj.put("ip", instance.getIp());
            ipObj.put("port", instance.getPort());
            ipObj.put("clusterName", instance.getClusterName());
            ipObj.put("instanceId", instance.getInstanceId());
            ipObj.put("serviceName", instance.getServiceName());
            hosts.add(ipObj);

        }
        result.put("name", serviceName);
        result.replace("hosts", hosts);
        return result;
    }


    /**
     * 解析实例
     * @param request
     * @return
     * @throws Exception
     */
    private Instance parseInstance(HttpServletRequest request){

        String serviceName =request.getParameter(CommonParams.SERVICE_NAME);
        Instance instance = getIpAddress(request);
        instance.setServiceName(serviceName);
        instance.setInstanceId(instance.generateInstanceId());
        instance.setLastBeat(System.currentTimeMillis());
        return instance;
    }

    private Instance getIpAddress(HttpServletRequest request) {

        boolean enabled = Boolean.parseBoolean(WebUtils.optional(request, "enabled", StringUtils.EMPTY));
        String weight = WebUtils.optional(request, "weight", "1");
        boolean healthy = Boolean.parseBoolean(WebUtils.optional(request, "healthy", "true"));
        Instance instance = getBasicIpAddress(request);
        instance.setWeight(Double.parseDouble(weight));
        instance.setHealthy(healthy);
        instance.setEnabled(enabled);
        return instance;
    }

    private Instance getBasicIpAddress(HttpServletRequest request) {

        final String ip = WebUtils.required(request, "ip");
        final String port = WebUtils.required(request, "port");
        boolean ephemeral = Boolean.parseBoolean(WebUtils.optional(request, "ephemeral", "true"));
        String cluster = WebUtils.optional(request, CommonParams.CLUSTER_NAME, UtilsAndCommons.DEFAULT_CLUSTER_NAME);
        Instance instance = new Instance();
        instance.setPort(Integer.parseInt(port));
        instance.setIp(ip);
        instance.setEphemeral(ephemeral);
        instance.setClusterName(cluster);
        return instance;
    }

}
